import struct
import win32file
import win32api
import win32con
import pywintypes

IOCTL_GET_HCD_DRIVERKEY_NAME = 0x220424
IOCTL_USB_GET_ROOT_HUB_NAME = 0x220408
IOCTL_USB_GET_NODE_INFORMATION = 0x220408
IOCTL_USB_GET_NODE_CONNECTION_INFORMATION = 0x22040C
IOCTL_USB_GET_NODE_CONNECTION_DRIVERKEY_NAME = 0x220420
IOCTL_USB_GET_NODE_CONNECTION_NAME = 0x2204
IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION = 0x220410
USB_STRING_DESCRIPTOR_TYPE = 3
MAXIMUM_USB_STRING_LENGTH = 255
NoDeviceConnected = 0
DeviceConnected = 1

def getRootHubName(handle):
    buff = win32file.DeviceIoControl(
            handle,
            IOCTL_USB_GET_ROOT_HUB_NAME,
            None,
            6,
            None)
    #print len(buff), type(buff), [ord(i) for i in buff]
    actualLength, t = struct.unpack('LH', buff)

    buff = win32file.DeviceIoControl(
            handle,
            IOCTL_USB_GET_ROOT_HUB_NAME,
            None,
            actualLength,
            None)
    #print len(buff), type(buff), [ord(i) for i in buff]
    return buff[4:].decode('utf-16le')

def getHCDDriverKeyName(handle):
    buff = win32file.DeviceIoControl(
            handle,
            IOCTL_GET_HCD_DRIVERKEY_NAME,
            None,
            6,
            None)
    actualLength, t = struct.unpack('LH', buff)
    buff = win32file.DeviceIoControl(
            handle,
            IOCTL_GET_HCD_DRIVERKEY_NAME,
            None,
            actualLength,
            None)
    return buff[4:].decode('utf-16le')

def getDriverKeyName(handle, index):
    driverKeyName = chr(index)+'\0'*9
    try:
        buff = win32file.DeviceIoControl(
                handle,
                IOCTL_USB_GET_NODE_CONNECTION_DRIVERKEY_NAME,
                driverKeyName,
                10,
                None)
    except pywintypes.error as e:
        print e.strerror, index
        raw_input('Press Enter...')
        exit(1)

    connectIndex, actualLength, t = struct.unpack('LLH', buff)
    buff = win32file.DeviceIoControl(
            handle,
            IOCTL_USB_GET_NODE_CONNECTION_DRIVERKEY_NAME,
            driverKeyName,
            actualLength,
            None)
    return buff[8:].decode('utf-16le')


def openDevice(deviceName):
    try:
        handle = win32file.CreateFile( deviceName, 
                win32file.GENERIC_WRITE, 
                win32file.FILE_SHARE_WRITE,
                None, 
                win32file.OPEN_EXISTING, 
                0, 
                None)
    except pywintypes.error as e:
        #print e.strerror, name
        raw_input('Press Enter...')
        exit(1)
    return handle

def getStringDescriptor(handle, connectionIndex, serialNumber):
    stringDescReq = struct.pack('LBBHHH', 
            connectionIndex,
            0,
            0,
            (USB_STRING_DESCRIPTOR_TYPE<<8)|serialNumber,
            win32api.GetSystemDefaultLangID(),
            12
            )
    try:
        buff = win32file.DeviceIoControl(
                handle,
                IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION,
                stringDescReq,
                12+MAXIMUM_USB_STRING_LENGTH,
                None)
    except pywintypes.error as e:
        print e.strerror
    #print len(buff[12:]), 'len'
    #print [ord(i) for i in buff[12:]]
    if len(buff)>16:
        print buff[14:].decode('utf-16le')

for i in range(10):
    name = r"\\.\HCD{}".format(i)
    try:
        handle = win32file.CreateFile( name, 
                win32file.GENERIC_WRITE, 
                win32file.FILE_SHARE_WRITE,
                None, 
                win32file.OPEN_EXISTING, 
                0, 
                None)
    except pywintypes.error as e:
        #print e.strerror, name
        break
    print '\nDriverKey:', getHCDDriverKeyName(handle)
    rootHubName = getRootHubName(handle)
    print '\n    RootHub\n'
    print '    {}\n'.format(rootHubName)
    deviceName = r'\\.\{}'.format(rootHubName)
    deviceHandle = openDevice(deviceName)
    buff = win32file.DeviceIoControl(
            deviceHandle,
            IOCTL_USB_GET_NODE_INFORMATION,
            None,
            76, # sizeof USB_NODE_INFORMATION
            None)
    numberOfPorts = ord(buff[6])
    #print numberOfPorts
    for index in range(1, numberOfPorts+1):
        connectionInfo = chr(index)+'\0'*34
        try:
            buff = win32file.DeviceIoControl(
                    deviceHandle,
                    IOCTL_USB_GET_NODE_CONNECTION_INFORMATION,
                    connectionInfo,
                    34+11*30, #note 
                    None)
        except pywintypes.error as e:
            print e.winerror, e.funcname, e.strerror
            #exit()
        #print [ord(i) for i in buff]
        if ord(buff[31]) == NoDeviceConnected: ##why
            print '    [Port{}]{}'.format(index, 'NoDeviceConnected')

        if ord(buff[31]) == DeviceConnected:
            print '    [Port{}]{}'.format(index, getDriverKeyName(deviceHandle, index))
            serialNumber = ord(buff[20])
            getStringDescriptor(deviceHandle, index, serialNumber)

    deviceHandle.close()
    handle.close()
raw_input('Press Enter...')
